/**
 * Copyright (c) 2008, George Taylor
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this list of 
 *     conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of 
 *     conditions and the following disclaimer in the documentation and/or other materials 
 *     provided with the distribution.
 *   * Neither the name of the copyright holder's organization nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software without specific 
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package uk.co.taylorconsulting.annoweb.web.builder;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.log4j.Logger;

import uk.co.taylorconsulting.annoweb.annotation.form.validation.Required;
import uk.co.taylorconsulting.annoweb.annotation.web.DefaultForward;
import uk.co.taylorconsulting.annoweb.annotation.web.Forward;
import uk.co.taylorconsulting.annoweb.annotation.web.Forwards;
import uk.co.taylorconsulting.annoweb.annotation.web.GlobalForward;
import uk.co.taylorconsulting.annoweb.web.builder.data.ActionHolder;
import uk.co.taylorconsulting.annoweb.web.builder.data.Globals;
import uk.co.taylorconsulting.annoweb.web.builder.data.Site;

public class GlobalDataBuilder extends SiteBuilder {
	private static final Logger            LOG      = Logger.getLogger(GlobalDataBuilder.class);
	private static final GlobalDataBuilder instance = new GlobalDataBuilder();
	
	public static void buildSite(HashMap<String, Site> sites, File tmpLocation) {
		instance.buildSite(sites, tmpLocation, true);
	}
	
	public void buildSite(HashMap<String, Site> sites, File tmpLocation, boolean ok) {
		File siteFile = new File(tmpLocation, "GlobalDataHandler.java");
		StringBuffer str = addClassStart(sites);
		str.append("}\n");
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(siteFile);
			fos.write(str.toString().getBytes());
			fos.close();
		} catch (Exception e) {
			LOG.warn("Error writing class to " + siteFile.getAbsolutePath() + "(" + e.getMessage() + ")");
		}
	}
	
	private StringBuffer addClassStart(HashMap<String, Site> sites) {
		StringBuffer str = new StringBuffer("import javax.servlet.http.*;\n");
		str.append("import javax.servlet.*;\n");
		str.append("import java.util.*;\n");
		str.append("import org.apache.log4j.Logger;\n");
		str.append("public class GlobalDataHandler extends uk.co.taylorconsulting.web.controller.WebHelper {\n");
		str.append("\tprivate static final Logger LOG = Logger.getLogger(GlobalDataHandler.class);\n");
		str.append("\tprivate static final ").append("GlobalDataHandler instance = new ").append("GlobalDataHandler").append("();\n");
		str.append("\tprivate ").append("GlobalDataHandler(){}\n");
		str.append("\tpublic static GlobalDataHandler getInstance() {\n");
		str.append("\t\treturn instance;\n");
		str.append("\t}\n");
		boolean first = true;
		str.append("\tpublic static boolean globalForward(String forwardName,HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) throws Throwable {\n");
		str.append("\t\treturn instance.globalForward(forwardName, request, response, servletContext, true);\n");
		str.append("\t}\n");
		str.append("\tpublic boolean globalForward(String forwardName,HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, boolean ok) throws Throwable {\n");
		first = true;
		Site s = sites.get("xxDefaultxx");
		Globals globals = s.getGlobals();
		for (GlobalForward f : globals.getForwards()) {
			if (first) {
				str.append("\t\tif(");
				first = false;
			} else {
				str.append("\t\t} else if(");
			}
			str.append("\"").append(f.name()).append("\".equals(forwardName)) {\n");
			addForward(str, f.path(), f.web(), f.method(), f.redirect(), "\t\t\t", s, "true");
		}
		str.append("\t\t}\n");
		str.append("\t\treturn false;\n");
		str.append("\t}\n");
		str.append("\tpublic static void handleException(Throwable t,HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {\n");
		str.append("\t\tinstance.handleException(t,request, response, servletContext,0, true);\n");
		str.append("\t}\n");
		str.append("\tpublic static void handleException(Throwable t,HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, int tries) {\n");
		str.append("\t\tinstance.handleException(t,request, response, servletContext,tries, true);\n");
		str.append("\t}\n");
		str.append("\tpublic void handleException(Throwable t,HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, int tries, boolean ok) {\n");
		str.append("\t\trequest.setAttribute(\"exception\",t);\n");
		str.append("\t\ttry {\n");
		str.append("\t\t\tresponse.reset();\n");
		first = true;
		for (Class<? extends Throwable> clazz : processHandlers(globals.getExceptionHandlers())) {
			if (first) {
				first = false;
				str.append("\t\tif(");
			} else {
				str.append("\t\t} else if(");
			}
			str.append("t instanceof ").append(clazz.getCanonicalName()).append(") {\n");
			Method m = globals.getExceptionHandler(clazz);
			str.append("\t\t\t").append(m.getDeclaringClass().getCanonicalName()).append(" actionClass = (").append(m.getDeclaringClass().getCanonicalName()).append(")getCache(\"").append(m.getDeclaringClass().getCanonicalName()).append(
			        "\");\n");
			if (m.getReturnType() == String.class) {
				str.append("\t\t\tString retValue = actionClass.");
			} else {
				str.append("\t\t\tactionClass.");
			}
			str.append(m.getName()).append("(").append(buildMethodSigniture(m, m.getAnnotation(Required.class), false, s.getGlobals())).append(");\n");
			ActionHolder ah = new ActionHolder(null, null, s);
			if (m.getAnnotation(Forward.class) != null) {
				Forward f = m.getAnnotation(Forward.class);
				ah.addForward(f);
			}
			if (m.getAnnotation(Forwards.class) != null) {
				Forwards fs = m.getAnnotation(Forwards.class);
				for (Forward f : fs.value()) {
					ah.addForward(f);
				}
			}
			if (m.getAnnotation(DefaultForward.class) != null) {
				ah.setDefaultForward(m.getAnnotation(DefaultForward.class));
			}
			if (m.getReturnType() == String.class) {
				// test value to forward
				boolean firstP = true;
				if (m.getAnnotation(Forward.class) != null) {
					if (firstP) {
						str.append("\t\t\t");
						firstP = false;
					} else {
						str.append("\t\t\t} else ");
					}
					Forward f = m.getAnnotation(Forward.class);
					addForward(str, f.path(), f.web(), f.method(), f.redirect(), "\t\t\t\t", s, null);
				}
				if (m.getAnnotation(Forwards.class) != null) {
					Forwards fs = m.getAnnotation(Forwards.class);
					for (Forward f : fs.value()) {
						if (firstP) {
							str.append("\t\t\t");
							firstP = false;
						} else {
							str.append("\t\t\t} else ");
						}
						addForward(str, f.path(), f.web(), f.method(), f.redirect(), "\t\t\t\t", s, null);
					}
				}
				if (firstP) {
					str.append("\t\t\t");
					firstP = false;
				} else {
					str.append("\t\t\t} else ");
				}
				str.append("if (!GlobalDataHandler.globalForward(retValue, request, response, servletContext)) {\n");
				// do default forwards
				addDefaultForward(s, str, ah, "\t\t\t\t");
			} else {
				// send to default forward
				addDefaultForward(s, str, ah, "\t\t\t");
			}
		}
		str.append("\t\t}\n");
		str.append("\t\t} catch (Throwable t2) {t2.printStackTrace();}\n");
		str.append("\t}\n");
		return str;
	}
	
	private ArrayList<Class<? extends Throwable>> processHandlers(HashMap<Class<? extends Throwable>, Method> exceptionHandlers) {
		ArrayList<Class<? extends Throwable>> allHandlers = new ArrayList<Class<? extends Throwable>>();
		ArrayList<Class<? extends Throwable>> toBeDone = new ArrayList<Class<? extends Throwable>>();
		toBeDone.addAll(exceptionHandlers.keySet());
		while (toBeDone.size() > 0) {
			Class<? extends Throwable> throwable = toBeDone.remove(0);
			boolean found = false;
			for (Class<? extends Throwable> test : toBeDone) {
				if (throwable.isAssignableFrom(test)) {
					found = true;
					break;
				}
			}
			if (found) {
				toBeDone.add(throwable);
			} else {
				allHandlers.add(throwable);
			}
		}
		return allHandlers;
	}
}
